package com.x.provider.customer.service.customer;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.x.core.utils.ApiAssetUtil;
import com.x.core.utils.CompareUtils;
import com.x.core.web.api.ResultCode;
import com.x.kafka.MqTemplate;
import com.x.provider.api.customer.enums.AccountTypeEnum;
import com.x.provider.api.customer.model.dto.customer.CustomerUserNameUpdateReqDTO;
import com.x.provider.api.mc.model.dto.ValidateVerificationCodeDTO;
import com.x.provider.api.mc.service.SmsRpcService;
import com.x.provider.api.oss.service.GreenRpcService;
import com.x.provider.api.oss.service.OssRpcService;
import com.x.provider.customer.configure.CustomerApplicationConfig;
import com.x.provider.customer.enums.CustomerErrorCode;
import com.x.provider.customer.enums.SystemRoleNameEnum;
import com.x.provider.customer.mapper.*;
import com.x.provider.customer.model.ao.*;
import com.x.provider.customer.model.domain.customer.*;
import com.x.provider.customer.model.query.customer.CustomerAccountQuery;
import com.x.provider.customer.model.query.customer.CustomerQuery;
import com.x.provider.customer.service.common.RedisKeyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CustomerServiceImpl implements CustomerService {

    private static final Map<String, String> DEFAULT_CUSTOMER_ATTRIBUTE = new HashMap<>();
    private static final Map<Integer, CustomerErrorCode> ERROR_CODE_CUSTOMER_ACCOUNT_EXIST = Map.of(AccountTypeEnum.PHONE.getValue(), CustomerErrorCode.USER_PHONE_EXISTED,
            AccountTypeEnum.USER_NAME.getValue(), CustomerErrorCode.USER_NAME_EXISTED);
    private final RedisKeyService redisKeyService;
    private final CustomerMapper customerMapper;
    private final CustomerPasswordMapper customerPasswordMapper;
    private final CustomerRoleMapper customerRoleMapper;
    private final RoleMapper roleMapper;
    private final PasswordEncoderService passwordEncoderService;
    private final GreenRpcService greenRpcService;
    private final CustomerApplicationConfig applicationConfig;
    private final SmsRpcService smsRpcService;
    private final MqTemplate mqTemplate;
    private final OssRpcService ossRpcService;
    private final CustomerRelationService customerRelationService;
    private final CustomerStatService customerStatService;
    private final AuthenticationService authenticationService;
    private final CustomerAccountMapper customerAccountMapper;


    public CustomerServiceImpl(RedisKeyService redisKeyService,
                               CustomerMapper customerMapper,
                               CustomerPasswordMapper customerPasswordMapper,
                               CustomerRoleMapper customerRoleMapper,
                               RoleMapper roleMapper,
                               PasswordEncoderService passwordEncoderService,
                               GreenRpcService greenRpcService,
                               CustomerApplicationConfig applicationConfig,
                               SmsRpcService smsRpcService,
                               MqTemplate mqTemplate,
                               OssRpcService ossRpcService,
                               CustomerRelationService customerRelationService,
                               CustomerStatService customerStatService,
                               AuthenticationService authenticationService,
                               CustomerAccountMapper customerAccountMapper){
        this.redisKeyService = redisKeyService;
        this.customerMapper =customerMapper;
        this.customerPasswordMapper = customerPasswordMapper;
        this.customerRoleMapper = customerRoleMapper;
        this.roleMapper = roleMapper;
        this.passwordEncoderService = passwordEncoderService;
        this.greenRpcService = greenRpcService;
        this.applicationConfig = applicationConfig;
        this.smsRpcService = smsRpcService;
        this.mqTemplate = mqTemplate;
        this.ossRpcService = ossRpcService;
        this.customerRelationService = customerRelationService;
        this.customerStatService = customerStatService;
        this.authenticationService = authenticationService;
        this.customerAccountMapper = customerAccountMapper;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(UserNamePasswordRegisterAO userNamePasswordRegisterAO) {
//        Customer customerExisted = getCustomer(userNamePasswordRegisterAO.getUserName());
//        ApiAssetUtil.isNull(customerExisted, UserResultCode.USER_NAME_EXISTED);
//        Customer customer = new Customer(userNamePasswordRegisterAO.getUserName());
//        registerCustomer(customer);
//        CustomerPassword customerPassword = new CustomerPassword(customer.getId(), RandomUtil.randomNumbers(4));
//        customerPassword.setPassword(passwordEncoderService.encode(userNamePasswordRegisterAO.getPassword(), customerPassword.getPasswordSalt()));
//        customerPasswordMapper.insert(customerPassword);
//        sendCustomerInfoChanged(customer, CustomerEvent.EventTypeEnum.ADD);
    }

    @Override
    public String loginByPassword(LoginByPasswordAO userNamePasswordLoginAO) {
        Customer customer = getCustomer(0L, userNamePasswordLoginAO.getUserName(), userNamePasswordLoginAO.getPhone(), null);
        ApiAssetUtil.notNull(customer, CustomerErrorCode.USER_NAME_OR_PWD_ERROR);
        ApiAssetUtil.isTrue(customer.getActive(), CustomerErrorCode.CUSTOMER_NOT_ACTIVE);
        CustomerPassword customerPassword = getCustomerPassword(customer.getId());
        ApiAssetUtil.isTrue(passwordEncoderService.matches(userNamePasswordLoginAO.getPassword(), customerPassword.getPasswordSalt(), customerPassword.getPassword()), CustomerErrorCode.USER_NAME_OR_PWD_ERROR);
        String token = authenticationService.signIn(customer);
        return token;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String loginOrRegisterBySms(LoginOrRegBySmsAO loginOrRegByPhoneAO) {
        smsRpcService.validateVerificationCode(ValidateVerificationCodeDTO.builder().phoneNumber(loginOrRegByPhoneAO.getPhoneNumber()).sms(loginOrRegByPhoneAO.getSmsVerificationCode()).build());
        Customer customer = getCustomerByPhone(loginOrRegByPhoneAO.getPhoneNumber());
        if (customer == null){
            customer = Customer.builder().build();
            registerCustomer(customer);
        }
        String token = authenticationService.signIn(customer);
        return token;
    }

    @Override
    public Customer registerCustomer(Customer customer){
        customerMapper.insert(customer);
        Role registeredRole = getRole(SystemRoleNameEnum.REGISTERED.name());
        CustomerRole customerRole = new CustomerRole(customer.getId(), registeredRole.getId());
        customerRoleMapper.insert(customerRole);
        insertDefaultUserName(customer.getId());
        return customer;
    }

    @Override
    public List<Customer> listCustomer(List<Long> idList) {
        return customerMapper.selectList(buildQuery(CustomerQuery.builder().idList(idList).build()));
    }

    /**
     * 验证用户待绑定的手机号是否合法
     * <li>1. 验证这个用户有没有绑定手机(仅保险起见)</li>
     * <li>2. 验证有没有其他用户用这个手机号绑定</li>
     * <li>3. 给这个手机号发验证码</li>
     * @param customerId 用户id
     * @param validatePhoneAO 待绑定的手机号
     */
    @Override
    public void checkPhoneBound(long customerId, ValidatePhoneAO validatePhoneAO) {
//        Customer customerExisted = getCustomer(customerId);
//        // 判断用户是不是没绑定手机号
//        ApiAssetUtil.isStringEmpty(customerExisted.getPhone(), UserResultCode.USER_PHONE_BOUND);
//        // 用户未绑定手机号
//        customerExisted = getCustomer(0L, null, validatePhoneAO.getPhone(), null);
//        // 判断是否是未被绑定的
//        ApiAssetUtil.isNull(customerExisted, UserResultCode.USER_PHONE_BOUND);
//        // 发送验证码
//        smsRpcService.sendVerificationCode(SendVerificationCodeDTO.builder().phoneNumber(validatePhoneAO.getPhone()).build());
    }

    /**
     * 用户绑定手机, 必须调用子接口validate, 验证手机号是否已被绑定
     * <li>1. 验证验证码正确与否</li>
     * <li>2. 插入数据库</li>
     * @param customerId 用户id
     * @param bindPhoneAO 传入待绑定号码和短信验证码
     */
    @Override
    public void bindPhone(long customerId, BindPhoneAO bindPhoneAO) {
        // 用户必须是没有绑定手机号的
//        Customer customer = getCustomer(customerId);
//        smsRpcService.validateVerificationCode(ValidateVerificationCodeDTO.builder().phoneNumber(bindPhoneAO.getPhone()).sms(bindPhoneAO.getSms()).build());
//        customer.setPhone(bindPhoneAO.getPhone());
//        customerMapper.updateById(customer);
//        sendCustomerInfoChanged(customer, CustomerEvent.EventTypeEnum.ADD);
    }



    @Override
    public void changePassword(long customerId, ChangePasswordByOldPasswordAO changePasswordAO) {
        CustomerPassword customerPassword = getCustomerPassword(customerId);
        if (customerPassword == null){
            customerPassword = new CustomerPassword(customerId, RandomUtil.randomNumbers(4));
            customerPassword.setPassword(passwordEncoderService.encode(changePasswordAO.getNewPassword(), customerPassword.getPasswordSalt()));
            customerPasswordMapper.insert(customerPassword);
        }
        else {
            customerPassword.setPassword(passwordEncoderService.encode(changePasswordAO.getNewPassword(), customerPassword.getPasswordSalt()));
            customerPasswordMapper.updateById(customerPassword);
        }
    }

    @Override
    public void changePhone(long customerId, ChangePhoneAO changePhoneAO) {
//        final Customer customer = getCustomer(customerId, null, changePhoneAO.getOldPhone(), null);
//        ApiAssetUtil.notNull(customer, UserResultCode.USER_PHONE_ERROR);
//        // 验证码校验可以放在if里也可以放在if外, 放在if里效率高, 放在if外不会有未被利用的验证码
//        smsRpcService.validateVerificationCode(ValidateVerificationCodeDTO.builder().phoneNumber(changePhoneAO.getPhone()).sms(changePhoneAO.getSms()).build());
//        if (!customer.getPhone().equals(changePhoneAO.getPhone())) {
//            customer.setPhone(changePhoneAO.getPhone());
//            Customer customerExisted = getCustomerByPhone(changePhoneAO.getPhone());
//            ApiAssetUtil.isNull(customerExisted, UserResultCode.USER_PHONE_EXISTED);
//            customerMapper.updateById(customer);
//            sendCustomerInfoChanged(customer, CustomerEvent.EventTypeEnum.ADD);
//        }
    }

    @Override
    public void changeUserName(CustomerUserNameUpdateReqDTO customerUserNameUpdateDTO) {
        final Customer customer = getCustomer(customerUserNameUpdateDTO.getCustomerId());
        Customer customerExisted = getCustomer(customerUserNameUpdateDTO.getUserName());
        ApiAssetUtil.isNull(customerExisted, CustomerErrorCode.USER_NAME_EXISTED);
        customer.setUserName(customerUserNameUpdateDTO.getUserName());
        customerMapper.updateById(customer);
    }

    @Override
    public Customer getCustomer(String userName) {
        return getCustomer(0, userName, null, null);
    }

    @Override
    public Customer getCustomer(long id) {
        return getCustomer(id, null, null, null);
    }

    @Override
    public CustomerPassword getCustomerPassword(long customerId) {
        return getCustomerPassword(0, customerId);
    }

    @Override
    public List<Role> listCustomerRole(long customerId) {
        List<CustomerRole> customerRoles = customerRoleMapper.selectList(new LambdaQueryWrapper<CustomerRole>().eq(CustomerRole::getCustomerId, customerId));
        if (customerRoles.isEmpty()){
            return Collections.emptyList();
        }
        return roleMapper.selectBatchIds(customerRoles.stream().map(CustomerRole::getRoleId).collect(Collectors.toList()));
    }


    public Customer getCustomer(long id, String userName, String phone, String email){
        CustomerQuery query = CustomerQuery.builder().id(id).userName(userName).phone(phone).email(email).build();
        LambdaQueryWrapper<Customer> customerLambdaQueryWrapper = buildQuery(query);
        return customerMapper.selectOne(customerLambdaQueryWrapper);
    }

    public Map<Long, Customer> listCustomer(CustomerQuery customerQuery){
        return customerMapper.selectList(buildQuery(customerQuery)).stream().collect(Collectors.toMap(item -> item.getId(), item -> item));
    }

    private LambdaQueryWrapper<Customer> buildQuery(CustomerQuery customerQuery) {
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
        if (CompareUtils.gtZero(customerQuery.getId())){
            queryWrapper.eq(Customer::getId, customerQuery.getId());
        }
        if (!CollectionUtils.isEmpty(customerQuery.getIdList())){
            queryWrapper.in(Customer::getId, customerQuery.getIdList());
        }
        if (!StringUtils.isEmpty(customerQuery.getUserName())){
            queryWrapper.eq(Customer::getUserName, customerQuery.getUserName());
        }
        return queryWrapper;
    }

    public CustomerPassword getCustomerPassword(long id, long customerId){
        LambdaQueryWrapper<CustomerPassword> customerPasswordWrapper = new LambdaQueryWrapper<>();
        if (customerId > 0){
            customerPasswordWrapper.eq(CustomerPassword::getCustomerId, customerId);
        }
        return customerPasswordMapper.selectOne(customerPasswordWrapper);
    }

    @Override
    public Role getRole(String systemName) {
        return getRole(0, systemName);
    }

    public Role getRole(long id, String systemName){
        LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (id > 0){
            roleLambdaQueryWrapper.eq(Role::getId, id);
        }
        if (!StringUtils.isEmpty(systemName)){
            roleLambdaQueryWrapper.eq(Role::getSystemName, systemName);
        }
        return roleMapper.selectOne(roleLambdaQueryWrapper);
    }

    private Customer getCustomerByPhone(String phone){
        return getCustomer(0L, null, phone, null);
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertDefaultUserName(Long customerId){
        int retryCount = 0;
        if (get(CustomerAccountQuery.builder().customerId(customerId).accountType(AccountTypeEnum.USER_NAME.getValue()).build()) != null){
            return;
        }
        boolean insertDefaultUserNameSuccess = false;
        while (retryCount ++ < applicationConfig.getGenUserNameMaxRetryCount()){
            String userName = genUserName();
            CustomerAccount customerAccount = get(CustomerAccountQuery.builder().accountType(AccountTypeEnum.USER_NAME.getValue()).account(userName).build());
            if (customerAccount != null){
                continue;
            }
            try {
                setCustomerAccount(customerId, userName, AccountTypeEnum.USER_NAME.getValue());
                insertDefaultUserNameSuccess = true;
                break;
            } catch (Exception e){
                continue;
            }
        }
        ApiAssetUtil.isTrue(insertDefaultUserNameSuccess, ResultCode.FAILED);
    }

    public void setCustomerAccount(Long customerId, String account, Integer accountType){
        CustomerAccount customerAccountExisted = get(CustomerAccountQuery.builder().account(account).accountType(accountType).build());
        ApiAssetUtil.isTrue(customerAccountExisted == null, ERROR_CODE_CUSTOMER_ACCOUNT_EXIST.get(accountType));
        CustomerAccount customerAccount = get(CustomerAccountQuery.builder().customerId(customerId).accountType(accountType).build());
        if (customerAccount == null){
            customerAccount = CustomerAccount.builder().customerId(customerId).accountType(accountType).account(account).build();
            customerAccountMapper.insert(customerAccount);
        }
        else{
            customerAccount.setAccount(account);
            customerAccountMapper.updateById(customerAccount);
        }
    }

    private String genUserName(){
        return RandomUtil.randomNumbers(applicationConfig.getDefaultUserNameLength());
    }

    public CustomerAccount get(CustomerAccountQuery query){
        return customerAccountMapper.selectOne(build(query));
    }

    private LambdaQueryWrapper<CustomerAccount> build(CustomerAccountQuery customerAccountQuery){
        LambdaQueryWrapper<CustomerAccount> queryWrapper = new LambdaQueryWrapper<>();
        if (customerAccountQuery.getCustomerId() != null){
            queryWrapper.eq(CustomerAccount::getCustomerId, customerAccountQuery.getCustomerId());
        }
        if (customerAccountQuery.getAccountType() != null){
            queryWrapper.eq(CustomerAccount::getAccountType, customerAccountQuery.getAccountType());
        }
        if (!StringUtils.isEmpty(customerAccountQuery.getAccount())){
            queryWrapper.eq(CustomerAccount::getAccount, customerAccountQuery.getAccount());
        }
        return queryWrapper;
    }
}
